package utils;

import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.Arrays;

public class Matrix {
    private Double M11;
    private Double M12;
    private Double M13;
    private Double M14;

    private Double M21;
    private Double M22;
    private Double M23;
    private Double M24;

    private Double M31;
    private Double M32;
    private Double M33;
    private Double M34;

    private Double M41;
    private Double M42;
    private Double M43;
    private Double M44;

    public static Matrix _identity() {
        return new Matrix
                (
                        1d, 0d, 0d, 0d,
                        0d, 1d, 0d, 0d,
                        0d, 0d, 1d, 0d,
                        0d, 0d, 0d, 1d
                );
    }

    public Matrix(Double m11, Double m12, Double m13, Double m14,
                  Double m21, Double m22, Double m23, Double m24,
                  Double m31, Double m32, Double m33, Double m34,
                  Double m41, Double m42, Double m43, Double m44) {
        this.M11 = m11;
        this.M12 = m12;
        this.M13 = m13;
        this.M14 = m14;

        this.M21 = m21;
        this.M22 = m22;
        this.M23 = m23;
        this.M24 = m24;

        this.M31 = m31;
        this.M32 = m32;
        this.M33 = m33;
        this.M34 = m34;

        this.M41 = m41;
        this.M42 = m42;
        this.M43 = m43;
        this.M44 = m44;
    }

    public Matrix(Double[] values) {
        this.M11 = values[0];
        this.M12 = values[1];
        this.M13 = values[2];
        this.M14 = values[3];

        this.M21 = values[4];
        this.M22 = values[5];
        this.M23 = values[6];
        this.M24 = values[7];

        this.M31 = values[8];
        this.M32 = values[9];
        this.M33 = values[10];
        this.M34 = values[11];

        this.M41 = values[12];
        this.M42 = values[13];
        this.M43 = values[14];
        this.M44 = values[15];

    }

    /**
     * Creates a translation matrix.
     *
     * @param xPosition The amount to translate on the X-axis.
     * @param yPosition The amount to translate on the Y-axis.
     * @param zPosition The amount to translate on the Z-axis.
     * @return The translation matrix.
     */
    public static Matrix CreateTranslation(Double xPosition, Double yPosition, Double zPosition) {
        Matrix result = Matrix._identity();

        result.M11 = 1.0d;
        result.M12 = 0.0d;
        result.M13 = 0.0d;
        result.M14 = 0.0d;
        result.M21 = 0.0d;
        result.M22 = 1.0d;
        result.M23 = 0.0d;
        result.M24 = 0.0d;
        result.M31 = 0.0d;
        result.M32 = 0.0d;
        result.M33 = 1.0d;
        result.M34 = 0.0d;

        result.M41 = xPosition;
        result.M42 = yPosition;
        result.M43 = zPosition;
        result.M44 = 1.0d;

        return result;
    }

    /**
     * Creates a uniform scaling matrix that scales equally on each axis.
     *
     * @param scale The uniform scaling factor.
     * @return The scaling matrix.
     */
    public static Matrix CreateScale(Double scale) {
        Matrix result = Matrix._identity();

        result.M11 = scale;
        result.M12 = 0.0d;
        result.M13 = 0.0d;
        result.M14 = 0.0d;
        result.M21 = 0.0d;
        result.M22 = scale;
        result.M23 = 0.0d;
        result.M24 = 0.0d;
        result.M31 = 0.0d;
        result.M32 = 0.0d;
        result.M33 = scale;
        result.M34 = 0.0d;
        result.M41 = 0.0d;
        result.M42 = 0.0d;
        result.M43 = 0.0d;
        result.M44 = 1.0d;

        return result;
    }

    /**
     * Creates a matrix for rotating points around the Z-axis.
     *
     * @param radians The amount, in radians, by which to rotate around the Z-axis.
     * @return The rotation matrix.
     */
    public static Matrix CreateRotationZ(double radians) {
        Matrix result = Matrix._identity();

        var c = Math.cos(radians);
        var s = Math.sin(radians);

        // [  c  s  0  0 ]
        // [ -s  c  0  0 ]
        // [  0  0  1  0 ]
        // [  0  0  0  1 ]
        result.M11 = c;
        result.M12 = s;
        result.M13 = 0.0d;
        result.M14 = 0.0d;
        result.M21 = -s;
        result.M22 = c;
        result.M23 = 0.0d;
        result.M24 = 0.0d;
        result.M31 = 0.0d;
        result.M32 = 0.0d;
        result.M33 = 1.0d;
        result.M34 = 0.0d;
        result.M41 = 0.0d;
        result.M42 = 0.0d;
        result.M43 = 0.0d;
        result.M44 = 1.0d;

        return result;
    }

    /**
     * Get the coordinate of the point transformed by the matrix.
     *
     * @param point point to be transformed.
     * @return the transformed point coordinate.
     */
    public Double[] Transform(Double @NotNull [] point) {
        Double x, y, z;
        x = point[0];
        y = point[1];
        z = point.length == 2 ? 0 : point[2];
        x = x * M11 + y * M21 + z * M31 + M41;
        y = x * M12 + y * M22 + z * M32 + M42;
        z = x * M13 + y * M23 + z * M33 + M43;
        return point.length == 2 ? new Double[]{x, y} : new Double[]{x, y, z};

    }

    public ArrayList<Double> Transform(ArrayList<Double> point) {
        Double x, y, z;
        x = point.get(0);
        y = point.get(1);
        z = point.get(2);
        x = x * M11 + y * M21 + z * M31 + M41;
        y = x * M12 + y * M22 + z * M32 + M42;
        z = x * M13 + y * M23 + z * M33 + M43;
        return new ArrayList<Double>(Arrays.asList(x, y, z));

    }

    /**
     * Multiplies the matrix by another matrix.
     *
     * @param value1 The first source matrix.
     * @param value2 The second source matrix.
     * @return The result of the multiplication.
     */
    public static Matrix Multiply(Matrix value1, Matrix value2) {
        Matrix m = Matrix._identity();
        // First row
        m.M11 = value1.M11 * value2.M11 + value1.M12 * value2.M21 + value1.M13 * value2.M31 + value1.M14 * value2.M41;
        m.M12 = value1.M11 * value2.M12 + value1.M12 * value2.M22 + value1.M13 * value2.M32 + value1.M14 * value2.M42;
        m.M13 = value1.M11 * value2.M13 + value1.M12 * value2.M23 + value1.M13 * value2.M33 + value1.M14 * value2.M43;
        m.M14 = value1.M11 * value2.M14 + value1.M12 * value2.M24 + value1.M13 * value2.M34 + value1.M14 * value2.M44;

        // Second row
        m.M21 = value1.M21 * value2.M11 + value1.M22 * value2.M21 + value1.M23 * value2.M31 + value1.M24 * value2.M41;
        m.M22 = value1.M21 * value2.M12 + value1.M22 * value2.M22 + value1.M23 * value2.M32 + value1.M24 * value2.M42;
        m.M23 = value1.M21 * value2.M13 + value1.M22 * value2.M23 + value1.M23 * value2.M33 + value1.M24 * value2.M43;
        m.M24 = value1.M21 * value2.M14 + value1.M22 * value2.M24 + value1.M23 * value2.M34 + value1.M24 * value2.M44;

        // Third row
        m.M31 = value1.M31 * value2.M11 + value1.M32 * value2.M21 + value1.M33 * value2.M31 + value1.M34 * value2.M41;
        m.M32 = value1.M31 * value2.M12 + value1.M32 * value2.M22 + value1.M33 * value2.M32 + value1.M34 * value2.M42;
        m.M33 = value1.M31 * value2.M13 + value1.M32 * value2.M23 + value1.M33 * value2.M33 + value1.M34 * value2.M43;
        m.M34 = value1.M31 * value2.M14 + value1.M32 * value2.M24 + value1.M33 * value2.M34 + value1.M34 * value2.M44;

        // Fourth row
        m.M41 = value1.M41 * value2.M11 + value1.M42 * value2.M21 + value1.M43 * value2.M31 + value1.M44 * value2.M41;
        m.M42 = value1.M41 * value2.M12 + value1.M42 * value2.M22 + value1.M43 * value2.M32 + value1.M44 * value2.M42;
        m.M43 = value1.M41 * value2.M13 + value1.M42 * value2.M23 + value1.M43 * value2.M33 + value1.M44 * value2.M43;
        m.M44 = value1.M41 * value2.M14 + value1.M42 * value2.M24 + value1.M43 * value2.M34 + value1.M44 * value2.M44;

        return m;
    }

    /**
     * Multiplies a matrix by a scalar value.
     *
     * @param value The scaling factor.
     * @return The scaled matrix.
     */
    public Matrix Multiply(Double value) {
        M11 = M11 * value;
        M12 = M12 * value;
        M13 = M13 * value;
        M14 = M14 * value;
        M21 = M21 * value;
        M22 = M22 * value;
        M23 = M23 * value;
        M24 = M24 * value;
        M31 = M31 * value;
        M32 = M32 * value;
        M33 = M33 * value;
        M34 = M34 * value;
        M41 = M41 * value;
        M42 = M42 * value;
        M43 = M43 * value;
        M44 = M44 * value;
        return this;
    }

    public Double Determinant() {
        return M11 * M22 * M33 * M44 - M11 * M22 * M34 * M43 +
                M11 * M23 * M34 * M42 - M11 * M23 * M32 * M44 +
                M11 * M24 * M32 * M43 - M11 * M24 * M33 * M42 -
                M12 * M23 * M34 * M41 + M12 * M23 * M31 * M44 -
                M12 * M24 * M31 * M43 + M12 * M24 * M33 * M41 -
                M12 * M21 * M33 * M44 + M12 * M21 * M34 * M43 +
                M13 * M24 * M31 * M42 - M13 * M24 * M32 * M41 +
                M13 * M21 * M32 * M44 - M13 * M21 * M34 * M42 +
                M13 * M22 * M34 * M41 - M13 * M22 * M31 * M44 -
                M14 * M21 * M32 * M43 + M14 * M21 * M33 * M42 -
                M14 * M22 * M33 * M41 + M14 * M22 * M31 * M43 -
                M14 * M23 * M31 * M42 + M14 * M23 * M32 * M41;
    }

    public static Matrix Inverse(Matrix matrix) {
        Double a = matrix.M11, b = matrix.M12, c = matrix.M13, d = matrix.M14;
        Double e = matrix.M21, f = matrix.M22, g = matrix.M23, h = matrix.M24;
        Double i = matrix.M31, j = matrix.M32, k = matrix.M33, l = matrix.M34;
        Double m = matrix.M41, n = matrix.M42, o = matrix.M43, p = matrix.M44;

        Double kp_lo = k * p - l * o;
        Double jp_ln = j * p - l * n;
        Double jo_kn = j * o - k * n;
        Double ip_lm = i * p - l * m;
        Double io_km = i * o - k * m;
        Double in_jm = i * n - j * m;

        Double a11 = +(f * kp_lo - g * jp_ln + h * jo_kn);
        Double a12 = -(e * kp_lo - g * ip_lm + h * io_km);
        Double a13 = +(e * jp_ln - f * ip_lm + h * in_jm);
        Double a14 = -(e * jo_kn - f * io_km + g * in_jm);

        Double det = a * a11 + b * a12 + c * a13 + d * a14;

        var result = Matrix._identity();

        Double invDet = 1d/det;

        result.M11 = a11 * invDet;
        result.M21 = a12 * invDet;
        result.M31 = a13 * invDet;
        result.M41 = a14 * invDet;

        result.M12 = -(b * kp_lo - c * jp_ln + d * jo_kn) * invDet;
        result.M22 = +(a * kp_lo - c * ip_lm + d * io_km) * invDet;
        result.M32 = -(a * jp_ln - b * ip_lm + d * in_jm) * invDet;
        result.M42 = +(a * jo_kn - b * io_km + c * in_jm) * invDet;

        Double gp_ho = g * p - h * o;
        Double fp_hn = f * p - h * n;
        Double fo_gn = f * o - g * n;
        Double ep_hm = e * p - h * m;
        Double eo_gm = e * o - g * m;
        Double en_fm = e * n - f * m;

        result.M13 = +(b * gp_ho - c * fp_hn + d * fo_gn) * invDet;
        result.M23 = -(a * gp_ho - c * ep_hm + d * eo_gm) * invDet;
        result.M33 = +(a * fp_hn - b * ep_hm + d * en_fm) * invDet;
        result.M43 = -(a * fo_gn - b * eo_gm + c * en_fm) * invDet;

        Double gl_hk = g * l - h * k;
        Double fl_hj = f * l - h * j;
        Double fk_gj = f * k - g * j;
        Double el_hi = e * l - h * i;
        Double ek_gi = e * k - g * i;
        Double ej_fi = e * j - f * i;

        result.M14 = -(b * gl_hk - c * fl_hj + d * fk_gj) * invDet;
        result.M24 = +(a * gl_hk - c * el_hi + d * ek_gi) * invDet;
        result.M34 = -(a * fl_hj - b * el_hi + d * ej_fi) * invDet;
        result.M44 = +(a * fk_gj - b * ek_gi + c * ej_fi) * invDet;

        return result;

    }

    public ArrayList<Double> ToArrayList() {
        return new ArrayList<Double>(Arrays.asList(this.M11, this.M12, this.M13, this.M14, this.M21, this.M22, this.M23, this.M24, this.M31, this.M32, this.M33, this.M34, this.M41, this.M42, this.M43,
                this.M44));
    }


}
